package com.xiyuan.smartutils;

import com.xiyuan.smartutils.constants.CodeConstants;
import com.xiyuan.smartutils.exceptions.IORuntimeException;
import com.xiyuan.smartutils.filters.Filter;
import com.xiyuan.smartutils.filters.FilterEndsWith;
import com.xiyuan.smartutils.streams.BOMInputStream;
import com.xiyuan.smartutils.text.StrJoiner;

import java.io.*;
import java.net.URI;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.FileAttribute;
import java.util.Objects;
import java.util.*;
import java.util.function.Consumer;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * 文件操作工具类
 *
 * @version v1.0.0 @author lgz 2017-8-27 新建与整理
 */

@SuppressWarnings({"all"})
public final class Files implements CodeConstants {
    
    /**
     * 绝对路径判断正则
     */
    private static final Pattern PATTERN_PATH_ABSOLUTE = Pattern.compile("^[a-zA-Z]:([/\\\\].*)?");
    
    private Files() {}
    /*****************************************************************************************/
    // 文件目录存在判断
    /*****************************************************************************************/
    /**
     * 修复路径<br>
     * 如果原路径尾部有分隔符，则保留为标准分隔符（/），否则不保留
     * <ol>
     * <li>1. 统一用 /</li>
     * <li>2. 多个 / 转换为一个 /</li>
     * <li>3. 去除左边空格</li>
     * <li>4. .. 和 . 转换为绝对路径，当..多于已有路径时，直接返回根路径</li>
     * </ol>
     * <p>
     * 栗子：
     *
     * <pre>
     * "/foo//" =》 "/foo/"
     * "/foo/./" =》 "/foo/"
     * "/foo/../bar" =》 "/bar"
     * "/foo/../bar/" =》 "/bar/"
     * "/foo/../bar/../baz" =》 "/baz"
     * "/../" =》 "/"
     * "foo/bar/.." =》 "foo"
     * "foo/../bar" =》 "bar"
     * "foo/../../bar" =》 "bar"
     * "//server/foo/../bar" =》 "/server/bar"
     * "//server/../bar" =》 "/bar"
     * "C:\\foo\\..\\bar" =》 "C:/bar"
     * "C:\\..\\bar" =》 "C:/bar"
     * "~/foo/../bar/" =》 "~/bar/"
     * "~/../bar" =》 普通用户运行是'bar的home目录'，ROOT用户运行是'/bar'
     * </pre>
     *
     * @param path 原路径
     * @return 修复后的路径
     */
    public static String normalize(String path) {
        if (path == null) {
            return null;
        }
        
        // 兼容Spring风格的ClassPath路径，去除前缀，不区分大小写
        String pathToUse = StrUtils.trimLeft(path, CLASSPATH_URL_PREFIX, true);
        // 去除file:前缀
        pathToUse = StrUtils.trimLeft(pathToUse, FILE_URL_PREFIX, true);
        
        // 识别home目录形式，并转换为绝对路径
        if (StrUtils.startsWith(pathToUse, '~')) {
            
            pathToUse = Systems.getUserHome() + pathToUse.substring(1);
        }
        
        // 统一使用斜杠
        pathToUse = pathToUse.replaceAll("[/\\\\]+", StrUtils.SLASH);
        // 去除开头空白符，末尾空白符合法，不去除
        pathToUse = StrUtils.trimLeft(pathToUse);
        // 兼容Windows下的共享目录路径（原始路径如果以\\开头，则保留这种路径）
        if (path.startsWith("\\\\")) {
            pathToUse = "\\" + pathToUse;
        }
        
        String prefix = _EMPTY_;
        int prefixIndex = pathToUse.indexOf(StrUtils.COLON);
        if (prefixIndex > -1) {
            // 可能Windows风格路径
            prefix = pathToUse.substring(0, prefixIndex + 1);
            if (StrUtils.startsWith(prefix, StrUtils.C_SLASH)) {
                // 去除类似于/C:这类路径开头的斜杠
                prefix = prefix.substring(1);
            }
            if (!prefix.contains(StrUtils.SLASH)) {
                pathToUse = pathToUse.substring(prefixIndex + 1);
            }
            else {
                // 如果前缀中包含/,说明非Windows风格path
                prefix = _EMPTY_;
            }
        }
        if (pathToUse.startsWith(StrUtils.SLASH)) {
            prefix += StrUtils.SLASH;
            pathToUse = pathToUse.substring(1);
        }
        
        String[] pathList = StrUtils.split(pathToUse, StrUtils.SLASH);
        List<String> pathElements = new LinkedList<>();
        int tops = 0;
        String element;
        for (int i = pathList.length - 1; i >= 0; i--) {
            element = pathList[i];
            // 只处理非.的目录，即只处理非当前目录
            if (!StrUtils.DOT.equals(element)) {
                if (StrUtils.DOUBLE_DOT.equals(element)) {
                    tops++;
                }
                else {
                    if (tops > 0) {
                        // 有上级目录标记时按照个数依次跳过
                        tops--;
                    }
                    else {
                        // Normal path element found.
                        pathElements.add(0, element);
                    }
                }
            }
        }
        
        // issue#1703@Github
        if (tops > 0 && StrUtils.isEmpty(prefix)) {
            // 只有相对路径补充开头的..，绝对路径直接忽略之
            while (tops-- > 0) {
                // 遍历完节点发现还有上级标注（即开头有一个或多个..），补充之
                // Normal path element found.
                pathElements.add(0, StrUtils.DOUBLE_DOT);
            }
        }
        
        return prefix + StrJoiner.of(StrUtils.SLASH).append(pathElements.iterator()).toString();
    }
    
    /**
     * 判断文件是否存在(包括目录和文件)
     *
     * @param path 文件或目录
     * @return true/false 存在则返回true
     */
    public static boolean exists(File path) {
        try {
            return path != null && path.exists();
        }
        catch (Throwable e) {
            // 有安全管理访问的访问异常，返回false;
            return false;
        }
    }
    
    /**
     * 判断文件是否存在(包括目录和文件)
     *
     * @param path 文件或目录完整路径
     * @return true/false 存在则返回true
     */
    public static boolean exists(String path) {
        return !Validates.isEmpty(path) && exists(new File(path));
    }
    
    /**
     * 判断目录下文件(包括目录和文件)是否存在
     *
     * @param dir  目录，目录本身不存在返回false
     * @param name 文件，文件名为空返回false
     * @return true/false 存在则返回true
     */
    public static boolean exists(File dir, String name) {
        return dir != null && Validates.isNotBlank(name) && exists(dir) && exists(new File(dir, name));
    }
    
    /**
     * 判断文件是否存在
     *
     * @param path 文件完整路径
     * @return true/false 存在则返回true
     */
    public static boolean isFile(String path) {
        return Validates.isNotBlank(path) && isFile(new File(path));
    }
    
    /**
     * 判断文件是否存在
     *
     * @param file 文件
     * @return true/false 存在则返回true
     */
    public static boolean isFile(File file) {
        return exists(file) && file.isFile();
    }
    
    /**
     * 判断路径是否为目录
     *
     * @param path 要判断的目录路径
     * @return true/false 如果是目录，返回true
     */
    public static boolean isDirectory(String path) {
        return Validates.isNotBlank(path) && isDirectory(new File(path));
    }
    
    /**
     * 判断路径是否为目录
     *
     * @param dir 目录对象
     * @return true/false 如果是目录，返回true
     */
    public static boolean isDirectory(File dir) {
        return exists(dir) && dir.isDirectory();
    }
    
    /*****************************************************************************************/
    // 文件路径
    /*****************************************************************************************/
    
    /**
     * 把Windows的Path替换成linux的path，即把\替换成/
     *
     * @param path Windows的Path
     * @return Linux的Path
     */
    public static String toLinuxPath(String path) {
        return Replaces.replaceAllEscape(path, "\\", "/");
    }
    
    /**
     * 把Windows的Path替换成linux的path，即把\替换成/
     *
     * @param file Windows的Path
     * @return Linux的Path
     */
    public static String toLinuxPath(File file) {
        try {
           
            return Replaces.replaceAllEscape(file.getCanonicalPath(), "\\", "/");
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    /**
     * 获取文件路径的绝对路径，return null表示异常
     *
     * @param path 文件相对路径
     * @return 文件绝对路径
     */
    public static String getCanonicalPath(String path) {
        return getCanonicalPath(new File(path));
    }
    
    /**
     * 获取文件路径的绝对路径，return null表示异常
     *
     * @param file 对象
     * @return 文件绝对路径
     */
    public static String getCanonicalPath(File file) {
        try {
            return file.getCanonicalPath();
        }
        catch (IOException e) {
            return null;
        }
    }
    
    /**
     * 获取文件扩展名,则取得文件后缀 注:这里只是找"."
     *
     * @param path 文件路径或文件名或URL路径
     * @return 文件后缀, 小写
     */
    public static String getFileExt(String path) {
        if (Validates.isBlank(path)) {return _EMPTY_;}
        
        path = path.trim();
        int pos = path.lastIndexOf(".");
        if (pos == -1) {return _EMPTY_;}
        
        String fileExt = path.substring(pos + 1);
        return fileExt.toLowerCase();
    }
    
    /**
     * 增加文件路径的扩展名，如果有扩展名
     *
     * @param fileExt 要新增的路径，为空返回原值，不为空+.+fileExt
     * @return 文件后缀名
     */
    public static String fixFileExt(String fileExt) {
        return Validates.isBlank(fileExt) ? _EMPTY_ : StrUtils.addStartsWith(StrUtils.trim(fileExt), ".");
    }
    
    /**
     * 提取一个文件路径的目录结构，不包含最后的分隔符， 如c:\\temp\\article.page，则返回c:\\temp
     *
     * @param filePath 文件完整路径
     * @return 目录结构
     */
    public static String getFileDirPath(String filePath) {
        String v = getFileDirPath(filePath, Systems.getFileSeparator());
        if (!Validates.isEmpty(v)) {return v;}
        
        // 如果未找到，再尝试使用默认/方式获到
        return getFileDirPath(filePath, "/");
    }
    
    /**
     * 提取一个文件路径的目录结构，不包含最后的分隔符， 如c:\\temp\\article.page，则返回c:\\temp
     *
     * @param filePath 文件完整路径
     * @param sep      指定分隔符
     * @return 目录结构
     */
    public static String getFileDirPath(String filePath, String sep) {
        int pos = filePath.lastIndexOf(sep);
        if (pos == -1) {return _EMPTY_;}
        
        return filePath.substring(0, pos);
    }
    
    /**
     * 提取一个文件路径的目录结构，包含最后的分隔符， 如c:\\temp\\article.page，则返回c:\\temp\\
     *
     * @param filePath 文件完整路径
     * @return 目录结构
     */
    public static String getFileDir(String filePath) {
        String v = getFileDir(filePath, Systems.getFileSeparator());
        if (!Validates.isEmpty(v)) {return v;}
        
        // 如果未找到，再尝试使用默认/方式获到
        return getFileDir(filePath, "/");
    }
    
    /**
     * 提取一个文件路径的目录结构，包含最后的分隔符， 如c:\\temp\\article.page，则返回c:\\temp\\
     *
     * @param filePath 文件完整路径
     * @param sep      指定分隔符
     * @return 目录结构
     */
    public static String getFileDir(String filePath, String sep) {
        int pos = filePath.lastIndexOf(sep);
        if (pos == -1) {return _EMPTY_;}
        return filePath.substring(0, pos + sep.length());
    }
    
    /**
     * 提取一个文件路径的文件名， 如c:\\temp\\article.jsp，则返回article.jsp
     *
     * @param filePath 文件完整路径
     * @return 文件名称
     */
    public static String getFileName(String filePath) {
        return getFileName(filePath, Systems.getFileSeparator());
    }
    
    /**
     * 提取一个文件路径的文件名， 如c:\\temp\\article.jsp，则返回article.jsp
     *
     * @param filePath 文件完整路径
     * @param sep      分隔符
     * @return 文件名称
     */
    public static String getFileName(String filePath, String sep) {
        int pos = filePath.lastIndexOf(sep);
        return pos == -1 ? getFileNameLinux(filePath) : filePath.substring(pos + 1);
    }
    
    /**
     * 提取一个文件路径的文件名，分隔符指定："/"， 如/opt/article.jsp，则返回article.jsp
     *
     * @param filePath 文件完整路径
     * @return 文件名称
     */
    public static String getFileNameLinux(String filePath) {
        int pos = filePath.lastIndexOf("/");
        return pos == -1 ? filePath : filePath.substring(pos + 1);
    }
    
    /*****************************************************************************************/
    // 文件夹操作
    /*****************************************************************************************/
    
    /**
     * 读取目录的所有文件子目录名称列表
     *
     * @param fileDir 目录路径
     * @return 文件目录列表，需要判断是否为null，=null表示目录不存在或不是目录
     */
    public static String[] getDirectoryList(String fileDir) {
        if (Validates.isBlank(fileDir)) {
            return null;
        }
        
        File dir = new File(fileDir.trim());
        if (!dir.exists() || !dir.isDirectory()) {
            return null;
        }
        
        return dir.list();
    }
    
    /**
     * 递归创建文件目录
     *
     * @param fileDir 要求创建的文件目录
     * @return true/false
     */
    public static boolean mkdir(File fileDir) {
        if (fileDir.exists() && fileDir.isDirectory()) {
            return true;
        }
        
        if (!fileDir.getParentFile().exists() || !fileDir.getParentFile().isDirectory()) {
            mkdir(fileDir.getParentFile());
        }
        
        return fileDir.mkdir();
    }
    
    /**
     * 递归创建文件目录
     *
     * @param fileDir 要求创建的文件目录
     * @return true/false
     */
    public static boolean mkdir(String fileDir) {
        return mkdir(new File(fileDir));
    }
    
    /**
     * 拷贝目录 成功返回true,不是目录或dest路径书写错误返回false
     *
     * @param src  源目录
     * @param dest 目标目录
     * @return 成功返回true, 不是目录或dest路径书写错误返回false
     */
    public static boolean copyDirectory(File src, String dest) throws Exception {
        if ((src == null) || !src.isDirectory()) {
            return false;
        }
        
        File destRoot = new File(dest);
        if (!destRoot.exists()) {
            destRoot.mkdir();
        }
        
        File[] entries = src.listFiles();
        int len = entries.length;
        for (int i = 0; i < len; i++) {
            File entry = entries[i];
            String target = dest + Systems.getFileSeparator() + entry.getName();
            if (entry.isDirectory()) {
                copyDirectory(entry, target); // 递归
            }
            else {
                File toFile = new File(target);
                copyFile(entry, toFile);
            }
        }
        
        return true;
    }
    
    /**
     * 递归删除文件夹及文件
     *
     * @param dir 文件夹路径
     * @return true/false
     */
    public static boolean deleteDirectory(String dir) {
        if (Validates.isBlank(dir)) {
            return false;
        }
        
        return deleteDirectory(new File(dir.trim()));
    }
    
    /**
     * 递归删除文件夹及文件
     *
     * @param dir 文件夹路径
     * @return true/false
     */
    public static boolean deleteDirectory(File dir) {
        if (dir == null || !dir.isDirectory()) {
            return false;
        }
        
        File[] entries = dir.listFiles();
        for (File entry : entries) {
            if (entry.isFile()) {// 文件
                entry.delete();
            }
            else {// 目录递归删除
                deleteDirectory(entry);
            }
        }
        
        return dir.delete();
    }
    
    /**
     * 递归删除文件夹及文件
     *
     * @param dir           文件夹路径
     * @param forbidDirList 禁止删除的目录名称列表
     * @return true/false
     */
    public static boolean deleteDirectory(String dir, List<String> forbidDirList) throws Exception {
        if (Validates.isNotBlank(dir)) {
            return false;
        }
        
        if (forbidDirList == null) {
            return deleteDirectory(new File(dir.trim()));
        }
        
        return deleteDirectory(new File(dir.trim()), forbidDirList);
    }
    
    /**
     * 递归删除文件夹
     *
     * @param dir           文件夹
     * @param forbidDirList 禁止删除的目录名称列表
     * @return true/false
     */
    public static boolean deleteDirectory(File dir, List<String> forbidDirList) throws Exception {
        if (dir == null || !dir.isDirectory()) {
            return false;
        }
        
        if (forbidDirList == null) {
            return deleteDirectory(dir);
        }
        
        boolean isAllowDeleteCurrentDir = true;
        File[] entries = dir.listFiles();
        for (File file : entries) {
            if (file.isFile()) {// 文件
                file.delete();
            }
            else {// 目录且不在禁止目录列表，递归删除
                boolean isAllowDelete = true;
                for (String forbid : forbidDirList) {
                    if (Validates.isMatch(file.getName(), forbid)) {
                        isAllowDelete = false;
                        isAllowDeleteCurrentDir = false;
                        break;
                    }
                }
                
                if (isAllowDelete) {
                    deleteDirectory(file, forbidDirList);
                }
            }
        }
        
        // 最后尝试删除目录，如果目录下没有禁止的则会成功，如果有禁止的是删不了则保留
        if (isAllowDeleteCurrentDir) {
            dir.delete();
        }
        
        return true;
    }
    
    /**
     * 检查父完整路径是否为自路径的前半部分，如果不是说明不是子路径，可能存在slip注入。
     * <p>
     * 见http://blog.nsfocus.net/zip-slip-2/
     *
     * @param parentFile 父文件或目录
     * @param file       子文件或目录
     * @return 子文件或目录
     * @throws IllegalArgumentException 检查创建的子文件不在父目录中抛出此异常
     */
    public static File checkSlip(File parentFile, File file) throws IllegalArgumentException {
        if (null != parentFile && null != file) {
            String parentCanonicalPath;
            String canonicalPath;
            try {
                parentCanonicalPath = parentFile.getCanonicalPath();
                canonicalPath = file.getCanonicalPath();
            }
            catch (IOException e) {
                // issue#I4CWMO@Gitee
                // getCanonicalPath有时会抛出奇怪的IO异常，此时忽略异常，使用AbsolutePath判断。
                parentCanonicalPath = parentFile.getAbsolutePath();
                canonicalPath = file.getAbsolutePath();
            }
            if (!canonicalPath.startsWith(parentCanonicalPath)) {
                throw new IllegalArgumentException("New file is outside of the parent dir: " + file.getName());
            }
        }
        return file;
    }
    /*****************************************************************************************/
    // 文件操作
    /*****************************************************************************************/
    
    /**
     * 创建文件，指定文件
     *
     * @param filePath 文件路径
     * @return =true表示创建成功，=false表示创建失败
     */
    public static boolean newFile(String filePath) {
        return newFile(new File(filePath));
    }
    
    /**
     * 创建文件，指定文件
     *
     * @param file 文件对象
     * @return =true表示创建成功，=false表示创建失败
     */
    public static boolean newFile(File file) {
        try {
            if (file.exists()) {
                return true;
            }
            
            String fileDir = file.getParent();
            Files.mkdir(fileDir);
            
            file.createNewFile();
            return true;
        }
        catch (Throwable e) {
            return false;
        }
    }
    
    /**
     * 文件重命名，指定文件重命名到新的文件，类似于文件剪切，要保证目标文件目录存在
     *
     * @param file 文件对象
     * @param dest 目标文件对象
     * @return =true表示成功，=false表示失败
     */
    public static boolean renameFile(File file, File dest) {
        return file.renameTo(dest);
    }
    
    /**
     * 文件重命名，指定文件重命名到新的文件，类似于文件剪切，要保证目标文件目录存在
     *
     * @param filePath 文件路径
     * @param destPath 目标文件路径
     * @return =true表示成功，=false表示失败
     */
    public static boolean renameFile(String filePath, String destPath) {
        return renameFile(new File(filePath), new File(destPath));
    }
    
    /**
     * 文件重命名新名称，原目录不变
     *
     * @param filePath    文件路径
     * @param newFileName 目标文件名
     * @return =true表示成功，=false表示失败
     */
    public static boolean renameFileName(String filePath, String newFileName) {
        String fileDir = getFileDir(filePath);
        String destPath = fileDir + newFileName;
        return renameFile(filePath, destPath);
    }
    
    /**
     * 文件拷贝，逐个字节拷贝到新的文件中件
     *
     * @param fromFile 源文件对象
     * @param toFile   目标文件对象
     * @return true/false
     */
    public static boolean copyFile(File fromFile, File toFile) throws Exception {
        if (!isFile(fromFile) || toFile == null) {
            return false;
        }
        
        FileLock iLock = null;
        FileLock oLock = null;
        
        try (FileInputStream fis = new FileInputStream(fromFile);
             FileOutputStream fos = new FileOutputStream(toFile);
             FileChannel iChannel = fis.getChannel();
             FileChannel oChannel = fos.getChannel()) {
            // 先尝试拿原文件共享锁
            iLock = iChannel.tryLock(0L, Long.MAX_VALUE, true);
            if (iLock == null || !iLock.isValid()) {
                return false;
            }
            
            // 再尝试拿目标文件占用锁
            oLock = oChannel.tryLock();
            if (oLock == null || !oLock.isValid()) {
                return false;
            }
            
            // 锁没问题作传输操作
            iChannel.transferTo(0, iChannel.size(), oChannel);
            return true;
        }
        finally {
            if (iLock != null) {
                iLock.release();
            }
            
            if (oLock != null) {
                oLock.release();
            }
            
        }
    }
    
    /**
     * 文件拷贝类,输入路径
     *
     * @param fromFile 源文件路径
     * @param toFile   目标文件路径
     * @return true/false
     */
    public static boolean copyFile(String fromFile, String toFile) throws Exception {
        if (Validates.isNotBlank(fromFile) || Validates.isNotBlank(toFile)) {
            return false;
        }
        
        File from = new File(fromFile.trim());
        File to = new File(toFile.trim());
        
        return copyFile(from, to);
    }
    
    /**
     * 文件拷贝,输入源文件路径,目标目录,文件名
     *
     * @param fromFile 源文件路径
     * @param toDir    目标目录
     * @param fileName 目录文件
     * @return true/false
     */
    public static boolean copyFile(String fromFile, String toDir, String fileName) throws Exception {
        if (Validates.isNotBlank(fromFile) || Validates.isNotBlank(toDir) || Validates.isNotBlank(fileName)) {
            return false;
        }
        
        File from = new File(fromFile.trim());
        if (!from.exists() || !from.isFile()) {
            return false;
        }
        
        // 新建目录
        toDir = toDir.trim();
        if (!Validates.isEmpty(toDir)) {
            File dir = new File(toDir);
            if (!dir.exists()) {
                dir.mkdirs();
            }
            
            dir = null;
        }
        
        if (toDir.lastIndexOf(Systems.getFileSeparator()) != toDir.length()) {
            toDir += Systems.getFileSeparator();
        }
        
        File to = new File(toDir + fileName.trim());
        return copyFile(from, to);
    }
    
    /**
     * 删除文件,文件不存在则返回true
     *
     * @param filePath 文件完整路径
     * @return true/false 成功删除则返回true
     */
    public static boolean deleteFile(String filePath) {
        if (Validates.isBlank(filePath)) {
            return false;
        }
        
        return deleteFile(new File(StrUtils.trim(filePath)));
    }
    
    /**
     * 删除文件,文件不存在则返回true
     *
     * @param file 文件
     * @return true/false 成功删除则返回true
     */
    public static boolean deleteFile(File file) {
        if (file == null) {
            return false;
        }
        
        if (!file.exists()) {
            return true;// 不存在直接返回成功
        }
        
        return file.delete();
    }
    
    /**
     * 删除文件，并按深度删除父级目录
     *
     * @param file  文件
     * @param depth 深度值
     * @throws IOException 异常
     */
    public static void deleteFile(File file, int depth) throws IOException {
        if (file.isFile() && !deleteFile(file)) {
            throw new IOException("文件删除失败：" + file);
        }
        
        File dir = file.getParentFile();
        for (int i = 0; i < depth; i++) {
            if (!dir.delete()) {
                break;
            }
            
            dir = dir.getParentFile();
        }
    }
    
    /**
     * 创建File对象，自动识别相对或绝对路径，相对路径将自动从ClassPath下寻找
     *
     * @param path 相对ClassPath的目录或者绝对路径目录
     * @return File
     */
    public static File file(String path) {
        if (null == path) {
            return null;
        }
        return new File(getAbsolutePath(path));
    }
    
    
    /**
     * 获取绝对路径<br>
     * 此方法不会判定给定路径是否有效（文件或目录存在）
     *
     * @param path      相对路径
     * @param baseClass 相对路径所相对的类
     * @return 绝对路径
     */
    public static String getAbsolutePath(String path, Class<?> baseClass) {
        String normalPath;
        if (path == null) {
            normalPath = StrUtils.EMPTY;
        }
        else {
            normalPath = normalize(path);
            if (isAbsolutePath(normalPath)) {
                // 给定的路径已经是绝对路径了
                return normalPath;
            }
        }
        
        // 相对于ClassPath路径
        final URL url = Resources.getResource(baseClass, normalPath);
        if (null != url) {
            // 对于jar中文件包含file:前缀，需要去掉此类前缀，在此做标准化，since 3.0.8 解决中文或空格路径被编码的问题
            return Files.normalize(Urls.getDecodedPath(url));
        }
        
        final String paths = Resources.getResource(StrUtils.EMPTY).getPath();
        final String classPath = Files.normalize(paths);
        if (null == classPath) {
            // throw new NullPointerException("ClassPath is null !");
            // 在jar运行模式中，ClassPath有可能获取不到，此时返回原始相对路径（此时获取的文件为相对工作目录）
            return path;
        }
        
        // 资源不存在的情况下使用标准化路径有问题，使用原始路径拼接后标准化路径
        return normalize(classPath.concat(Objects.requireNonNull(path)));
    }
    
    /**
     * 获取绝对路径，相对于ClassPath的目录<br>
     * 如果给定就是绝对路径，则返回原路径，原路径把所有\替换为/<br>
     * 兼容Spring风格的路径表示，例如：classpath:config/example.setting也会被识别后转换
     *
     * @param path 相对路径
     * @return 绝对路径
     */
    public static String getAbsolutePath(String path) {
        return getAbsolutePath(path, null);
    }
    
    /**
     * 获取标准的绝对路径
     *
     * @param file 文件
     * @return 绝对路径
     */
    public static String getAbsolutePath(File file) {
        if (file == null) {
            return null;
        }
        
        try {
            return file.getCanonicalPath();
        }
        catch (IOException e) {
            return file.getAbsolutePath();
        }
    }
    
    /**
     * 给定路径已经是绝对路径<br>
     * 此方法并没有针对路径做标准化，建议先执行{@link #normalize(String)}方法标准化路径后判断<br>
     * 绝对路径判断条件是：
     * <ul>
     *     <li>以/开头的路径</li>
     *     <li>满足类似于 c:/xxxxx，其中祖母随意，不区分大小写</li>
     *     <li>满足类似于 d:\xxxxx，其中祖母随意，不区分大小写</li>
     * </ul>
     *
     * @param path 需要检查的Path
     * @return 是否已经是绝对路径
     */
    public static boolean isAbsolutePath(String path) {
        if (StrUtils.isEmpty(path)) {
            return false;
        }
        
        // 给定的路径已经是绝对路径了
        return StrUtils.C_SLASH == path.charAt(0) || Regexps.isMatch(PATTERN_PATH_ABSOLUTE, path);
    }
    
    public static Path createTempDirectory(String prefix,
                                           FileAttribute<?>... attrs) throws IOException {
        return java.nio.file.Files.createTempDirectory(prefix, attrs);
    }
    
    /**
     * 创建File对象<br>
     * 此方法会检查slip漏洞，漏洞说明见http://blog.nsfocus.net/zip-slip-2/
     *
     * @param parent 父目录
     * @param path   文件路径
     * @return File
     */
    public static File file(String parent, String path) {
        return file(new File(parent), path);
    }
    
    /**
     * 创建File对象<br>
     * 根据的路径构建文件，在Win下直接构建，在Linux下拆分路径单独构建
     * 此方法会检查slip漏洞，漏洞说明见http://blog.nsfocus.net/zip-slip-2/
     *
     * @param parent 父文件对象
     * @param path   文件路径
     * @return File
     */
    public static File file(File parent, String path) {
        if (StrUtils.isBlank(path)) {
            throw new NullPointerException("File path is blank!");
        }
        return checkSlip(parent, buildFile(parent, path));
    }
    
    /**
     * 通过多层目录参数创建文件<br>
     * 此方法会检查slip漏洞，漏洞说明见http://blog.nsfocus.net/zip-slip-2/
     *
     * @param directory 父目录
     * @param names     元素名（多层目录名），由外到内依次传入
     * @return the file 文件
     */
    public static File file(File directory, String... names) {
        Asserts.nonNull(directory, "directory must not be null");
        if (Arrays.isEmpty(names)) {
            return directory;
        }
        
        File file = directory;
        for (String name : names) {
            if (null != name) {
                file = file(file, name);
            }
        }
        return file;
    }
    
    /**
     * 通过多层目录创建文件
     * <p>
     * 元素名（多层目录名）
     *
     * @param names 多层文件的文件名，由外到内依次传入
     * @return the file 文件
     */
    public static File file(String... names) {
        if (Arrays.isEmpty(names)) {
            return null;
        }
        
        File file = null;
        for (String name : names) {
            if (file == null) {
                file = file(name);
            }
            else {
                file = file(file, name);
            }
        }
        return file;
    }
    
    /**
     * 创建File对象
     *
     * @param uri 文件URI
     * @return File
     */
    public static File file(URI uri) {
        if (uri == null) {
            throw new NullPointerException("File uri is null!");
        }
        return new File(uri);
    }
    
    /**
     * 创建File对象
     *
     * @param url 文件URL
     * @return File
     */
    public static File file(URL url) {
        return new File(Urls.toURI(url));
    }
    
    /**
     * 根据压缩包中的路径构建目录结构，在Win下直接构建，在Linux下拆分路径单独构建
     *
     * @param outFile  最外部路径
     * @param fileName 文件名，可以包含路径
     * @return 文件或目录
     */
    private static File buildFile(File outFile, String fileName) {
        // 替换Windows路径分隔符为Linux路径分隔符，便于统一处理
        fileName = fileName.replace('\\', '/');
        if (!Systems.isWindows()
                // 检查文件名中是否包含"/"，不考虑以"/"结尾的情况
                && fileName.lastIndexOf(StrUtils.SLASH, fileName.length() - 2) > 0) {
            // 在Linux下多层目录创建存在问题，/会被当成文件名的一部分，此处做处理
            // 使用/拆分路径（zip中无\），级联创建父目录
            final List<String> pathParts = java.util.Arrays.stream(StrUtils.split(fileName, "/"))
                    .filter(r -> !r.isEmpty()).collect(Collectors.toList());
            final int lastPartIndex = pathParts.size() - 1;// 目录个数
            for (int i = 0; i < lastPartIndex; i++) {
                // 由于路径拆分，slip不检查，在最后一步检查
                outFile = new File(outFile, pathParts.get(i));
            }
            // noinspection ResultOfMethodCallIgnored
            outFile.mkdirs();
            // 最后一个部分如果非空，作为文件名
            fileName = pathParts.get(lastPartIndex);
        }
        return new File(outFile, fileName);
    }
    /*****************************************************************************************/
    // 读写文件内容
    /*****************************************************************************************/
    
    /**
     * 读取文件内容，并将内容以UTF-8字符串形式输出。 如果文件不存在，或路径错误，则返回NULL
     *
     * @param filePath 文件路径
     * @return 文件内容
     */
    public static String readUTF8(String filePath) {
        byte[] bytes = read(filePath);
        return (bytes == null) ? null : (bytes.length == 0) ? _EMPTY_ : StrUtils.newStringUTF8(bytes);
    }
    
    /**
     * 读取文件内容，并将内容以UTF-8字符串形式输出。 如果文件不存在，或路径错误，则返回NULL
     *
     * @param file 文件对象，最大不超过200M
     * @return 文件内容
     */
    public static String readUTF8(File file) {
        byte[] bytes = read(file);
        return (bytes == null) ? null : (bytes.length == 0) ? _EMPTY_ : StrUtils.newStringUTF8(bytes);
    }
    
    /**
     * 读取文件内容，并将内容以字符串形式输出。 如果文件不存在，或路径错误，则返回NULL
     *
     * @param filePath 文件路径，最大不超过200M
     * @param encoding 编码
     * @return 文件内容
     */
    public static String read(String filePath, String encoding) {
        byte[] bytes = read(filePath);
        return (bytes == null) ? null : (bytes.length == 0) ? _EMPTY_ : StrUtils.newString(bytes, encoding);
    }
    
    /**
     * 读取文件内容，并将内容以字符串形式输出。 如果文件不存在，或路径错误，则返回NULL
     *
     * @param file     文件对象，最大不超过200M
     * @param encoding 编码
     * @return 文件内容
     */
    public static String read(File file, String encoding) {
        byte[] bytes = read(file);
        return (bytes == null) ? null : (bytes.length == 0) ? _EMPTY_ : StrUtils.newString(bytes, encoding);
    }
    
    /**
     * 读取文件内容，并将内容以字符串形式输出。 如果文件不存在，或路径错误，则返回null
     *
     * @param filePath 文件路径，最大不超过200M
     * @return 文件内容
     */
    public static byte[] read(String filePath) {
        return read(new File(filePath));
    }
    
    /**
     * 读取文件内容，并将内容以字符串形式输出。 如果文件不存在，或路径错误，则返回null
     *
     * @param file 文件对象，最大不超过200M
     * @return 文件内容
     */
    public static byte[] read(File file) {
        return read(file, 200 * MiB);
    }
    
    /**
     * 读取文件内容，约定最大值，如指定4096，超过则返回错误
     *
     * @param file 文件对象
     * @param max  允许读取的文件大小，当文件大小超过该值是则抛出异常
     * @return 文件内容
     */
    public static byte[] read(File file, int max) {
        if (file == null || !file.isFile() || !file.canRead()) {
            return _EMPTY_B_;
        }
        
        Asserts.as(file.length() <= max ? null : "文件长度大于指定的大小[%d]", max);
        
        try (FileInputStream input = new FileInputStream(file)) {
            ByteArrayOutputStream output = new ByteArrayOutputStream();
            IoUtils.putBytes(input, output);
            return output.toByteArray();
        }
        catch (IOException e) {
            return null;
        }
    }
    
    /**
     * 从文件管道中读取指定的长度到缓存中
     *
     * @param fileChannel 文件管道
     * @param pos         文件起始位置
     * @param buffer      读数据到缓存中
     * @param off         缓存偏移值
     * @param len         读取长度
     * @throws IOException 异常
     */
    public static void read(FileChannel fileChannel, long pos, byte[] buffer, int off, int len) throws IOException {
        while (len > 0) {
            ByteBuffer buf = ByteBuffer.wrap(buffer, off, len);
            int count = fileChannel.read(buf, pos);
            if (count <= 0) {
                throw new EOFException("按长度读文件管道时,长度不够即到达流尾端");
            }
            
            pos += count;
            off += count;
            len -= count;
        }
    }
    
    /**
     * 从文件管道中读取可能的长度到缓存中
     *
     * @param fileChannel 文件管道
     * @param pos         文件起始位置
     * @param buffer      读数据到缓存中
     * @param off         缓存偏移值
     * @param len         读取长度
     * @throws IOException 异常
     */
    public static int readMaybe(FileChannel fileChannel, long pos, byte[] buffer, int off, int len) throws IOException {
        int oLen = len;
        while (len > 0) {
            ByteBuffer buf = ByteBuffer.wrap(buffer, off, len);
            int count = fileChannel.read(buf, pos);
            if (count <= 0) {
                return oLen - len;
            }
            
            pos += count;
            off += count;
            len -= count;
        }
        
        return oLen;
    }
    
    /**
     * 读取文件内容，并将内容以字符串形式输出。 如果文件不存在，或路径错误，则返回NULL
     *
     * @param filePath 文件路径
     * @return 文件内容
     */
    public static String readGZip(String filePath, String encoding) {
        byte[] bytes = read(filePath);
        if (bytes == null || bytes.length == 0) {
            return null;
        }
        
        return Zips.unGZipString(bytes, encoding);
    }
    
    
    public static List<String> readAllLines(String path) throws IOException {
        return readAllLines(new File(path));
    }
    
    public static List<String> readAllLines(String path, Charset chs) throws IOException {
        return readAllLines(new File(path), chs);
    }
    
    public static List<String> readAllLines(File file) throws IOException {
        return java.nio.file.Files.readAllLines(file.toPath());
    }
    
    public static List<String> readAllLines(File file, Charset chs) throws IOException {
        return java.nio.file.Files.readAllLines(file.toPath(), chs);
    }
    
    public static List<String> readAllLinesAsUTF8(File file) throws IOException {
        return java.nio.file.Files.readAllLines(file.toPath(), _UTF_8_C_);
    }
    
    public static List<String> readAllLinesAsUTF8(String path) throws IOException {
        return readAllLines(path, _UTF_8_C_);
    }
    
    /**
     * 根据文件路径,名称,把内容以字节数组的方式写入到指定的文件中
     *
     * @param filePath 文件路径
     * @param input    文件流，方法内不会关闭input
     * @return true/false 写入操作成功，返回true
     */
    public static long writeReturnSize(String filePath, InputStream input) {
        // 1.检查参数
        Asserts.nonBlank(filePath, "filePath");
        Asserts.nonNull(input, "input");
        
        // 2.检查文件目录
        String fileDirPath = getFileDirPath(filePath);
        Asserts.as(mkdir(fileDirPath) ? null : "写入文件时[" + filePath + "]目录创建失败");
        
        // 3.写入到文件中
        File file = new File(filePath);
        try (FileOutputStream output = new FileOutputStream(file)) {
            IoUtils.putBytes(input, output);
        }
        catch (Exception e) {
            throw Asserts.exception("写入文件时[" + filePath + "]，异常", e);
        }
        
        // 4.返回长度
        return file.length();
    }
    
    /**
     * 根据文件路径，写入到指定的文件中
     *
     * @param filePath 文件完整路径
     * @param bytes    文件内容
     * @return true/false 写入操作成功，返回true
     */
    public static long writeReturnSize(String filePath, byte[] bytes) {
        // 1.检查参数
        Asserts.nonBlank(filePath, "filePath");
        Asserts.nonNull(bytes, "bytes");
        
        // 2.检查文件目录
        String fileDirPath = getFileDirPath(filePath);
        Asserts.as(mkdir(fileDirPath) ? null : "写入文件时[" + filePath + "]目录创建失败");
        
        // 3.写入到文件中
        File file = new File(filePath);
        try (FileOutputStream fos = new FileOutputStream(file)) {
            fos.write(bytes);
            fos.flush();
        }
        catch (Exception e) {
            throw Asserts.exception("写入文件时[" + filePath + "]，异常", e);
        }
        
        // 4.返回长度
        return file.length();
    }
    
    /**
     * 根据文件路径,名称,把内容以字节数组的方式写入到指定的文件中
     *
     * @param filePath 文件完整路径
     * @param bytes    文件内容
     * @return true/false 写入操作成功，返回true
     */
    public static boolean write(String filePath, byte[] bytes) {
        // 1.检查参数
        Asserts.nonBlank(filePath, "filePath");
        Asserts.nonNull(bytes, "bytes");
        
        // 2.检查文件目录
        String fileDirPath = getFileDirPath(filePath);
        Asserts.as(mkdir(fileDirPath) ? null : "写入文件时[" + filePath + "]目录创建失败");
        
        // 3.写入到文件中
        try (FileOutputStream fos = new FileOutputStream(filePath)) {
            fos.write(bytes);
            fos.flush();
            return true;
        }
        catch (IOException ex) {
            Logs.warn("写入文件时[" + filePath + "]，异常");
            // Logs.warn();
            return false;
        }
    }
    
    /**
     * 根据文件路径,名称,把内容以字节数组的方式写入到指定的文件中
     *
     * @param file   文件对象
     * @param bytes  文件内容
     * @param append 是否追加
     * @return true/false 写入操作成功，返回true
     */
    public static boolean write(File file, byte[] bytes, boolean append) {
        // 1.检查参数
        Asserts.nonNull(file, "file");
        Asserts.nonNull(bytes, "bytes");
        
        // 2.检查文件目录
        Asserts.as(mkdir(file.getParentFile()) ? null : "写入文件时[" + file + "]目录创建失败");
        
        // 3.写入到文件中
        try (FileOutputStream fos = new FileOutputStream(file, append)) {
            fos.write(bytes);
            fos.flush();
            return true;
        }
        catch (IOException ex) {
            return false;
        }
    }
    
    /**
     * 根据文件路径,名称,把内容以流的方式写入到指定的文件中
     *
     * @param filePath 文件完整路径
     * @param content  文件内容
     * @param encoding 编码
     * @return true/false 写入操作成功，返回true
     */
    public static boolean write(String filePath, String content, String encoding) {
        try {
            return write(filePath, content.getBytes(encoding));
        }
        catch (UnsupportedEncodingException e) {
            return false;
        }
    }
    
    /**
     * 根据文件路径，指定UTF-8编码写入内容到文件
     *
     * @param filePath 文件完整路径
     * @param content  文件内容
     * @return true/false 写入操作成功，返回true
     */
    public static boolean writeUTF8(String filePath, String content) {
        return write(filePath, content, _UTF_8_);
    }
    
    /**
     * 根据文件路径，指定UTF-8编码写入内容到文件
     *
     * @param file    文件完整路径
     * @param content 文件内容
     * @param append  是否追加
     * @return true/false 写入操作成功，返回true
     */
    public static boolean writeUTF8(File file, String content, boolean append) {
        Asserts.nonNull(content, "content");
        
        return write(file, content.getBytes(_UTF_8_C_), append);
    }
    
    /*****************************************************************************************/
    // 文件搜索
    
    /*****************************************************************************************/
    
    private static int recursiveFiles(File dir, Consumer<File> handler) {
        int count = 0;
        File[] list = dir.isFile() ? new File[]{dir} : dir.listFiles();
        if (list == null) return count;
        for (int i = 0; i < list.length; i++) {
            File file = list[i];
            if (file.isFile()) {
                count++;
                handler.accept(file);
            }
            else if (file.isDirectory()) {
                recursiveFiles(file, handler);
            }
        }
        return count;
    }
    
    /**
     * 递归遍历 文件
     *
     * @param dir     指定搜索目录
     * @param handler 文件处理器
     */
    public static void forEach(File dir, Consumer<File> handler) {
        recursiveFiles(dir, handler);
    }
    
    /**
     * 递归扫描文件列表，包含子目录
     *
     * @param dir 指定搜索目录
     */
    public static void filter(File dir, Filter<File> filter, Consumer<File> handler) {
        recursiveFiles(dir, r -> {
            if ((filter == null || filter.accept(r)) && handler != null) {
                handler.accept(r);
            }
        });
    }
    
    /**
     * 递归获取文件列表
     *
     * @param filter 传入的文件列表引用，需先new，不允许为null;
     * @param dir    指定搜索目录
     * @param filter 过滤器
     * @return 文件列表
     */
    public static List<File> listFiles(File dir, Filter<File> filter) {
        List<File> fileList = new ArrayList<>();
        filter(dir, filter, fileList::add);
        return fileList;
    }
    
    /**
     * 递归初始化文件列表
     *
     * @param fileNameList 传入的文件列表引用，需先new，不允许为null;
     * @param dir          指定搜索目录
     * @param filter       过滤器
     */
    public static void queryFilterNameList(List<String> fileNameList, File dir, Filter<String> filter) {
        File[] list = dir.listFiles();
        if (dir.isFile() && filter.accept(dir.getName())) {
            fileNameList.add(dir.getName());
        }
        
        for (int i = 0; i < list.length; i++) {
            File file = list[i];
            if (file.isFile()) {
                if (filter.accept(file.getName())) {
                    fileNameList.add(file.getName());
                }
            }
            else {
                queryFilterNameList(fileNameList, file, filter);
            }
        }
    }
    
    /**
     * 递归初始化文件列表
     *
     * @param fileNameList 传入的文件列表引用，需先new，不允许为null;
     * @param dir          指定搜索目录
     * @param endsWith     后缀或以该结尾相同的数据,调用endsWith进行匹配，endsWith为null或空时表示全匹配
     */
    public static void queryEndsWithNameList(List<String> fileNameList, File dir, String endsWith) {
        queryFilterNameList(fileNameList, dir, new FilterEndsWith(endsWith));
    }
    
    /*****************************************************************************************/
    // 类文件路径取类名
    /*****************************************************************************************/
    
    /**
     * 通过类文件路径和类路径获取类名，异常返回null
     *
     * @param classPath 类路径
     * @param classFile 类文件路径
     * @return 类名
     */
    public static String getClassName(File classPath, File classFile) {
        try {
            String classPathDir = classPath.getCanonicalPath();
            String classFilePath = classFile.getCanonicalPath();
            
            return getClassName(classPathDir, classFilePath);
        }
        catch (IOException e) {
            return null;
        }
    }
    
    /**
     * 通过类文件路径和类路径获取类名
     *
     * @param classPath     类路径
     * @param classFilePath 类文件路径
     * @return 类名
     */
    public static String getClassName(String classPath,
                                      String classFilePath) {// 格式如：d:\xxxx\bin\org\xxxx\xxx\Global.class
        // 1.去除目录前缀,2.去除.class的后缀,3.把文件分隔符替换成.,4.把前面的.去掉
        String className = StrUtils.trimLeft(classFilePath, classPath);
        className = StrUtils.trimRight(className, ".class");
        className = Replaces.replaceAllEscape(className, Systems.getFileSeparator(), ".");
        className = StrUtils.trimLeft(className, ".");
        return className;
    }
    
    /*****************************************************************************************/
    // 根据日期生成文件路径
    /*****************************************************************************************/
    
    /**
     * 生成文件目录路径，以当前年月日组成一个路径，结果如UNIX:2012/3/2,2012/11/24,WIN:2012\3\\2,2012\\11\\2
     *
     * @return String 完整路径
     */
    public static String getPathByCurrentDate() {
        Calendar date = Calendar.getInstance();
        int year = date.get(Calendar.YEAR);
        int month = date.get(Calendar.MONTH) + 1;
        int day = date.get(Calendar.DATE);
        
        return year + Systems.getFileSeparator() + month + Systems.getFileSeparator() + day;
    }
    
    /**
     * 生成目录路径，以指定日期组成一个路径，结果如UNIX:2012/3/2,2012/11/24,WIN:2012\3\\2,2012\\11\\2
     *
     * @return String 完整路径
     */
    public static String getPathByCurrentDate(Date date) {
        Calendar calender = Calendar.getInstance();
        calender.setTime(date);
        
        int year = calender.get(Calendar.YEAR);
        int month = calender.get(Calendar.MONTH) + 1;
        int day = calender.get(Calendar.DATE);
        
        return year + Systems.getFileSeparator() + month + Systems.getFileSeparator() + day;
    }
    
    /**
     * 删除多余的日志文件并重命名fileName,fileName.1,fileName.2,fileName.3成fileName.1,fileName.2,fileName.3,fileName.4
     *
     * @param fileDir           文件目录
     * @param fileName          文件名称
     * @param fileMaxNum        文件最大个数
     * @param renameCurrentFile 是否重命令当前文件成fileName.1
     */
    public static void removeMaxFileAndRenameFile(String fileDir, String fileName, int fileMaxNum,
                                                  boolean renameCurrentFile) {
        if (fileMaxNum <= 0) {
            return;
        }
        
        fileDir = toLinuxPath(fileDir);
        fileDir = StrUtils.removeEndsWith(fileDir, "/");
        
        // 把>=fileMaxNum文件名的都删除，<fileMaxNum的都改名后缀加1
        HashMap<Integer, File> renameMap = new HashMap<Integer, File>();
        
        File dir = new File(fileDir);
        for (File file : dir.listFiles()) {
            if (!file.isFile() || !file.getName().startsWith(fileName + ".")) {
                continue;
            }
            
            String index = file.getName().substring(fileName.length() + 1);
            if (!Validates.isInteger(index)) {
                continue;
            }
            
            int ind = Ints.toInt(index);
            if (ind >= fileMaxNum) {
                file.delete();
            }
            else {
                renameMap.put(ind, file);
            }
        }
        
        // 小于fileMaxNum的文件改名。从大到小重命名 .1,.2,.3成.2,.3,.4
        String filePath = fileDir + "/" + fileName;
        for (int i = fileMaxNum - 1; i >= 1; i--) {
            File file = renameMap.get(i);
            if (file == null) {
                continue;
            }
            
            file.renameTo(new File(filePath + "." + (i + 1)));
        }
        
        // 最后重命名当前文件为.1
        if (renameCurrentFile && Files.exists(filePath)) {
            File file = new File(filePath);
            file.renameTo(new File(filePath + ".1"));
        }
    }
    
    /*****************************************************************************************/
    // 文件系统
    /*****************************************************************************************/
    
    /**
     * 判断文件是否有执行权限，Windows下返回false，其他系统判断
     *
     * @param file 文件
     * @return =true表示有，=false表示无权限
     */
    public static boolean canExecute(File file) {
        return !Systems.isWindows() && file.canExecute();
    }
    
    /**
     * 设置文件增加或删除执行权限，Windows下无效，POSIX下适用
     *
     * @param file       文件
     * @param executable 是否可执行
     * @return =true表示设置成功，=false表示设置失败
     */
    public static boolean setExecutable(File file, boolean executable) {
        return !Systems.isWindows() && file.setExecutable(executable);
    }
    
    /**
     * 设置文件/目录为隐藏属性，仅window有效
     *
     * @param path   文件/目录路径
     * @param hidden =true表示隐藏，=false表示取消
     */
    public static void setHidden(String path, boolean hidden) {
        setHidden(new File(path), hidden);
    }
    
    /**
     * 设置文件/目录为隐藏属性，仅window有效
     *
     * @param file   文件/目录
     * @param hidden =true表示隐藏，=false表示取消
     */
    public static void setHidden(File file, boolean hidden) {
        if (!Systems.isWindows() || !exists(file)) {
            return;
        }
        
        try {
            // attrib +H "c:\abc.text"
            String cmd = new StringBuilder().append("attrib ")
                    .append(hidden ? "+" : "-")
                    .append("H ")
                    .append("\"")
                    .append(file.getCanonicalPath())
                    .append("\"")
                    .toString();
            
            Windows.cmd(cmd);
        }
        catch (IOException e) {
            throw Asserts.exception(e);
        }
    }
    
    /**
     * 获取文件类型 <br>
     * <p>
     * default and exception return 'application/octet-stream'
     *
     * @param filePath 文件路径
     * @return mimeType,
     */
    public static String getMimeType(String filePath) {
        return getMimeType(new File(filePath));
    }
    
    /**
     * 获取文件类型
     * <p>
     * default and exception return 'application/octet-stream'
     *
     * @param file 文件路径
     * @return mimeType
     */
    public static String getMimeType(File file) {
        String type = "";
        try {
            type = java.nio.file.Files.probeContentType(Paths.get(file.toURI()));
        }
        catch (Exception e) {
            type = "application/octet-stream";
        }
        
        if (type == null || type.trim().length() == 0) {
            type = "application/octet-stream";
        }
        
        return type;
    }
    
    /**
     * 获得输入流
     *
     * @param file 文件
     * @return 输入流
     * @throws IORuntimeException 文件未找到
     */
    public static BufferedInputStream getInputStream(File file) throws IORuntimeException {
        return IoUtils.toBuffered(IoUtils.toStream(file));
    }
    
    /**
     * 获得输入流
     *
     * @param path 文件路径
     * @return 输入流
     * @throws IORuntimeException 文件未找到
     */
    public static BufferedInputStream getInputStream(String path) throws IORuntimeException {
        return getInputStream(file(path));
    }
    
    /**
     * 获得BOM输入流，用于处理带BOM头的文件
     *
     * @param file 文件
     * @return 输入流
     * @throws IORuntimeException 文件未找到
     */
    public static BOMInputStream getBOMInputStream(File file) throws IORuntimeException {
        try {
            return new BOMInputStream(new FileInputStream(file));
        }
        catch (IOException e) {
            throw new IORuntimeException(e);
        }
    }
    
    /**
     * 读取带BOM头的文件为Reader
     *
     * @param file 文件
     * @return BufferedReader对象
     */
    public static BufferedReader getBOMReader(File file) {
        return IoUtils.toReader(getBOMInputStream(file));
    }
    
    /**
     * 获得一个文件读取器
     *
     * @param file 文件
     * @return BufferedReader对象
     * @throws IORuntimeException IO异常
     */
    public static BufferedReader getUtf8Reader(File file) throws IORuntimeException {
        return getReader(file, _UTF_8_C_);
    }
    
    /**
     * 获得一个文件读取器
     *
     * @param path 文件路径
     * @return BufferedReader对象
     * @throws IORuntimeException IO异常
     */
    public static BufferedReader getUtf8Reader(String path) throws IORuntimeException {
        return getReader(path, _UTF_8_C_);
    }
    
    
    /**
     * 获得一个文件读取器
     *
     * @param file    文件
     * @param charset 字符集
     * @return BufferedReader对象
     * @throws IORuntimeException IO异常
     */
    public static BufferedReader getReader(File file, Charset charset) throws IORuntimeException {
        return IoUtils.toReader(getInputStream(file), charset);
    }
    
    
    /**
     * 获得一个文件读取器
     *
     * @param path    绝对路径
     * @param charset 字符集
     * @return BufferedReader对象
     * @throws IORuntimeException IO异常
     */
    public static BufferedReader getReader(String path, Charset charset) throws IORuntimeException {
        return getReader(file(path), charset);
    }
//    /**
//     * 通过文件头获取 文件类型
//     * <p>
//     * default and exception return 'application/octet-stream'
//     *
//     * @param headerBytes 文件头子节
//     * @return mimeType
//     */
//    public static String getMimeType(byte[] headerBytes) {
//        throw new IllegalStateException("功能开发中..");
//    }

//    public static void main(String[] args) {
//        System.out.println(getMimeType("C:\\Users\\Administrator\\Desktop\\Firefox.lnk"));
//    }
    
    
    /**
     * 获取文件魔数
     *
     * @param file
     * @return
     */
    public static String getMagic(File file, int len) {
        try {
            return getMagic(java.nio.file.Files.newInputStream(file.toPath()), len);
        }
        catch (Exception e) {
            return null;
        }
    }
    
    /**
     * 获取文件魔数
     *
     * @param input
     * @return
     */
    public static String getMagic(InputStream input, int len) {
        try {
            byte[] b = new byte[len < 4 ? 4 : len];
            if (input.read(b) != b.length) {
                return null;
            }
            return getMagic(b);
        }
        catch (Exception e) {
            return null;
        }
    }
    
    /**
     * 获取文件魔数
     *
     * @param bytes
     * @return
     */
    public static String getMagic(byte[] bytes) {
        if (bytes == null || bytes.length < 4) {
            return null;
        }
        
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02x", b));
        }
        
        return sb.toString();
    }
}
